package compilers.symbolTable;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import compilers.AST.Node;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;

public class SymbolTableScope {
	
	private HashMap<String, SymbolTableElement> fElements;
	private SymbolTableScope fParent;
	private LinkedList<SymbolTableScope> fChildren; 
	private Node fScopeNode;
	private int fFreeMemoryAddress;
	private boolean fIsFunction = false;
	private String fIdent = "";
	
	public SymbolTableScope() {
		fElements = new HashMap<String, SymbolTableElement>();
		fParent = null;
		fChildren = new LinkedList<SymbolTableScope>();
		fScopeNode = null;
		fFreeMemoryAddress = 0;
	}
	
	public SymbolTableScope(SymbolTableScope parentScope, Node scopeNode, boolean isFunction) {
		fElements = new HashMap<String, SymbolTableElement>();
		fParent = parentScope;
		fChildren = new LinkedList<SymbolTableScope>();
		fParent.addChild(this);
		fScopeNode = scopeNode;
		fIsFunction = isFunction;
		fFreeMemoryAddress = 5;
	}
	
	public SymbolTableScope(SymbolTableScope parentScope, Node scopeNode) {
		fElements = new HashMap<String, SymbolTableElement>();
		fParent = parentScope;
		fChildren = new LinkedList<SymbolTableScope>();
		fParent.addChild(this);
		fScopeNode = scopeNode;
		if (fParent != null) {
			//Non-global scope
			try {
				fFreeMemoryAddress = this.getFreeAddress();
			} catch(Exception e) {
				fFreeMemoryAddress = 5;
			}
		} else {
			//No parent, so this is the global scope. Addresses start from zero.
			fFreeMemoryAddress = 0;
		}
	}
	
	public void setIdent(String ident) {
		this.fIdent = ident;
	}
	
	public void addElement(SymbolTableElement element) throws SemanticalException {
		if (fElements.containsKey(element.getIdentifier())) {
			throw new SemanticalException("Redeclaration of identifier " + element.getIdentifier() + " is not allowed.", element.getLineNr());
		}
		element.setMemoryAddress(fFreeMemoryAddress);
		fFreeMemoryAddress++;
		if (element instanceof SymbolTableArrayElement) {
			fFreeMemoryAddress++;
		}
		fElements.put(element.getIdentifier(), element);
	}
	
	public void addFunctionDefinition(SymbolTableFunctionElement element, SymbolTableScope definitionScope) throws SemanticalException {
		if (fElements.containsKey(element.getIdentifier())) {
			SymbolTableFunctionElement forwardDecl = (SymbolTableFunctionElement) fElements.get(element.getIdentifier());
			if (forwardDecl.isDefinition()) {
				throw new SemanticalException("Redefinition of function " + element.getIdentifier() + " is not allowed.", element.getLineNr());
			} else {
				element.checkSemantics(forwardDecl, definitionScope);
				element.setNodeId(forwardDecl.getNodeId());
				if (definitionScope == this) {
					fElements.remove(element.getIdentifier());
				}
				definitionScope.addElement(element);
			}
		} else {
			if (fParent != null) {
				fParent.addFunctionDefinition(element, definitionScope);
			} else {
				definitionScope.addElement(element);
			}
		}
	}
	
	public void print(int identLevel) {
		String result = "";
		for (int i = 1; i <= identLevel; i++) {
			result += "  ";
		}
		if ((fElements.isEmpty()) && (fChildren.isEmpty())) {
			System.out.println(result + "{}");
		} else {
			System.out.println(result + "{");
			Iterator<SymbolTableElement> it = fElements.values().iterator();
			while (it.hasNext()) {
				it.next().print(identLevel);
			}
			Iterator<SymbolTableScope> it2 = fChildren.iterator();
			while(it2.hasNext()) {
				it2.next().print(identLevel+1);
			}
			System.out.println(result + "}");
		}
	}
	
	public void addChild(SymbolTableScope element) {
		fChildren.add(element);
	}
	
	public boolean hasScopeNode(Node scopeNode) {
		if (fScopeNode == scopeNode) {
			return true;
		}
		return false;
	}
	
	public Node getScopeNode() {
		return fScopeNode;
	}
	
	public SymbolTableScope getParent() {
		return fParent;
	}
	
	public SymbolTableElement getElement(String identifier, int nodeId) {
		if (fElements.containsKey(identifier)) {
			SymbolTableElement result = fElements.get(identifier);
			if (nodeId >= result.getNodeId()) {
				return fElements.get(identifier);
			} else {
				if (fParent != null) {
					return fParent.getElement(identifier, nodeId);
				} else {
					return null;
				}
			}
		} else {
			if (fParent != null) {
				return fParent.getElement(identifier, nodeId);
			} else {
				return null;
			}
		}
	}
	
	public SymbolTableScope getChildScope(Node scopeNode) {
		if (!fChildren.isEmpty()) {
			Iterator<SymbolTableScope> it = fChildren.iterator();
			while (it.hasNext()) {
				SymbolTableScope childScope = it.next();
				if (childScope.hasScopeNode(scopeNode)) {
					return childScope;
				}
			}
			return null;
		}
		return null;
	}
	
	public SymbolTableScope getSiblingScope() {
		if(!(fParent == null)) {
			LinkedList<SymbolTableScope> parentChildren = fParent.fChildren;
			Iterator<SymbolTableScope> childrenIt = parentChildren.iterator();
			while(childrenIt.hasNext()) {
				SymbolTableScope childScope = childrenIt.next();
				if(childScope == this) {
					if(childrenIt.hasNext()) {
						return childrenIt.next();
					}
				}
			}
		}
		return null;
	}
	
	public SymbolTableScope getPrevSiblingScope() {
		if(!(fParent == null)) {
			LinkedList<SymbolTableScope> parentChildren = fParent.fChildren;
			Iterator<SymbolTableScope> childrenIt = parentChildren.iterator();
			SymbolTableScope childScope = null;
			SymbolTableScope prev = null;
			while(childrenIt.hasNext()) {
				if(childScope != null) {
					prev = childScope;
				} else {
					prev = null;
				}
				childScope = childrenIt.next();
				if(prev != null) {
					if(childScope == this) {
						return prev;
					}
				}
			}
		}
		return null;
	}
	
	public int calcStaticElements() {
		int elems = 0;
		
		//Elements in this scope
		Iterator<SymbolTableElement> it = fElements.values().iterator();
		while (it.hasNext()) {
			SymbolTableElement temp = it.next();
			if(temp instanceof SymbolTableVariableElement) {
				elems++;
			} else if (temp instanceof SymbolTableArrayElement) {
				elems = elems + 2;
			}
		}
		
		//Elements in the scopes contained in this scope
		Iterator<SymbolTableScope> it2 = fChildren.iterator();
		while(it2.hasNext()) {
			SymbolTableScope sc = it2.next();
			//if (!sc.fIsFunction) {
				elems = elems + sc.calcStaticElements();
		//	}
		}
		
		// Elements + 5 if the scope is a function
		if (fIsFunction) {
			elems = elems + 5;
		}
		
		return elems;
	}
	
	// Calculates the number of memory that's already used by local variables and arrays and puts the result on top of the stack
	public void calcDynamicStartAddress(PGenerator pGen, int nodeId) {
		
		// Put the number of static parts on top of the stack
		pGen.load('i', Integer.toString(this.calcStaticElements()));
		
		// Calculate the dynamic array space that is already used, e.g. the arrays that occur before currentElement
		Iterator<SymbolTableElement> it = fElements.values().iterator();
		while (it.hasNext()) {
			SymbolTableElement temp = it.next();
			if (temp instanceof SymbolTableArrayElement) {
				if (nodeId > temp.getNodeId()) {
					pGen.lod('i', this.getDepth(), temp.getMemoryAddress() + 1);
					pGen.add('i');
				}
			}
		}
	
		// TODO Elements in the scopes contained in this scope
	
	}
	
	/*
	 * Returns the root scope
	 */
	
	public SymbolTableScope getRoot() {
		SymbolTableScope parent = this.fParent;
		if(parent == null) {
			return this;
		}
		while(parent.fParent != null) {
			parent = parent.fParent;
		}
		return parent;
	}
	
	/*
	 * Find the scope of a certain function
	 * Only looks for children of current scope! (so start at the root)
	 */
	
	public SymbolTableScope findFunctionScope(String ident) {
		if (!fChildren.isEmpty()) {
			Iterator<SymbolTableScope> it = fChildren.iterator();
			while (it.hasNext()) {
				SymbolTableScope childScope = it.next();
				if (childScope.fIdent.equals(ident)) {
						return childScope;
				}
			}
			
			it = fChildren.iterator();
			while (it.hasNext()) {
				SymbolTableScope childScope = it.next();
				SymbolTableScope foundScope = childScope.findFunctionScope(ident);
				if(foundScope != null) {
					return foundScope;
				}
			}
		}
		return null;
	}
	
	public Iterator<SymbolTableElement> getElements() {
		return fElements.values().iterator();
	}
	
	/*
	 * Determines the next free address in the scope of a function
	 */
	private int getFreeAddress() throws Exception {
		SymbolTableScope prevSib = this.getPrevSiblingScope();
		if(this.getPrevSiblingScope() == null) {
			//First child-scope within the scope, so look at the scope above
			return this.fParent.fFreeMemoryAddress;			
		} else {
			//There is already a child-scope within the scope, so look at that memory addresses
			return prevSib.fFreeMemoryAddress;
		}
	}
	
	/*
	 * Returns the depth of a function. Necessary for nesting purposes.
	 */
	
	public int getDepth() {
		int depth;

		//This makes sure we count the current function
		if(this.fIsFunction) {
			depth = 1;
		} else {
			depth = 0;
		}

		//See if any parent scopes are functions as well.
		SymbolTableScope parent = fParent;
		while(parent != null) {
			if(parent.fIsFunction) {
				depth++;
			}
			parent = parent.fParent;
		}

		return depth-1;
	}
}
